feat(investor): redesign campaigns UI, invest dialog, and route structure#60
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (72)
📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds a campaigns section and sidebar, simplifies root layout, redirects home to /campaigns, introduces CampaignsLayout wrapping children with RoiDashboardShell, refactors project listing to fetch/filter escrows, and updates InvestDialog and ROI shell assets and layout. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client as Client (Browser)
participant Campaigns as CampaignsPage
participant Shell as RoiDashboardShell
participant Toolbar as CampaignToolbar (dynamic)
participant List as ProjectList
participant Indexer as Escrow Indexer API
participant Dialog as InvestDialog
Client->>Campaigns: Navigate to /campaigns
Campaigns->>Shell: Render children
Campaigns->>Toolbar: Load dynamic toolbar (ssr:false)
Campaigns->>List: Provide search/filter props
List->>Indexer: Fetch escrows by contract IDs
Indexer-->>List: Return escrows
List->>List: Filter/memoize escrows
List->>ProjectCard: Render items
Client->>Dialog: Open InvestDialog (via ProjectCard)
Dialog->>Dialog: Compute estimatedReturn, totalAtMaturity
Dialog-->>Client: Submit investment
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan for PR comments
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (6)
apps/investor-tokenization/src/app/campaigns/page.tsx (1)
16-25: Move this page composition behind a feature entrypoint.This route now owns local UI state and rendering that belongs in
src/features, and it overlaps with theHomeViewcomposition changed in the same PR. A dedicatedCampaignsViewfeature component would keeppage.tsxas a thin wrapper and avoid drift. As per coding guidelines, "Organize Next.js frontends using feature-based folder structure insrc/features/".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/investor-tokenization/src/app/campaigns/page.tsx` around lines 16 - 25, The CampaignsPage currently owns local UI state and composition (useState, RoiHeader, CampaignToolbar, ProjectList); extract this composition into a new feature component (e.g., CampaignsView) under src/features so the view owns the state (search, filter) and renders <RoiHeader />, <CampaignToolbar /> and <ProjectList />; then simplify page.tsx to import and render the new CampaignsView as the default export, and update any imports/usages accordingly so state and rendering live in the feature folder and page.tsx remains a thin entrypoint.apps/investor-tokenization/src/app/roi/page.tsx (1)
17-35: Keep the route thin and move the view logic intosrc/features.
search/filterstate plus the campaign filtering logic now live in the route file. Extract this into a feature-level view component and letpage.tsxjust render it, so the App Router layer stays declarative. As per coding guidelines, "Organize Next.js frontends using feature-based folder structure insrc/features/".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/investor-tokenization/src/app/roi/page.tsx` around lines 17 - 35, The route file's state and view logic (useState hooks search/setSearch and filter/setFilter plus the useMemo filteredCampaigns that references mockCampaigns) should be extracted into a feature component under src/features (e.g. create src/features/roi/RoiView or RoiFeature) that owns the UI and filtering logic; update page.tsx (RoiPage) to be thin and only import and render the new component (pass props only if needed), move the filtering code and mockCampaigns usage into the new component (keeping function/component names like RoiView or RoiFeature and the filteredCampaigns logic intact), export the feature component as default or named export and remove the state/useMemo from RoiPage so the App Router route stays declarative.apps/investor-tokenization/src/components/shared/Sidebar.tsx (1)
15-26: Centralize the sidebar nav config instead of hard-coding a second copy here.These links now duplicate the
ROI_NAV_ITEMSalready defined inapps/investor-tokenization/src/features/roi/roi-dashboard-shell.tsx, which makes labels/routes easy to desynchronize. Export a shared nav model fromsrc/featuresand consume it from both sidebar shells. As per coding guidelines, "Organize Next.js frontends using feature-based folder structure insrc/features/".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/investor-tokenization/src/components/shared/Sidebar.tsx` around lines 15 - 26, The Sidebar component currently hard-codes a local links array (const links: SidebarLink[]) duplicating ROI_NAV_ITEMS from roi-dashboard-shell.tsx; instead, move the nav model into a shared export under src/features (e.g., export const ROI_NAV_ITEMS or a generic NAV_ITEMS in src/features/roi/nav.ts), import that shared constant in both apps/investor-tokenization/src/components/shared/Sidebar.tsx and apps/investor-tokenization/src/features/roi/roi-dashboard-shell.tsx, and replace the local links usage with the imported shared nav to keep labels/routes centralized and avoid desynchronization.apps/investor-tokenization/src/features/home/HomeView.tsx (1)
13-13: Use the@/alias for this internal import.
../transparency/ProjectListbreaks the app’s import convention and makes moves/refactors noisier. Switch it to@/features/transparency/ProjectList. As per coding guidelines, "Use path alias@/*mapping to./src/*for imports".Suggested change
-import { ProjectList } from "../transparency/ProjectList"; +import { ProjectList } from "@/features/transparency/ProjectList";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/investor-tokenization/src/features/home/HomeView.tsx` at line 13, Replace the relative import in HomeView.tsx that pulls ProjectList from "../transparency/ProjectList" with the project path alias import "@/features/transparency/ProjectList" so the file imports ProjectList using the `@/`* mapping; update the import statement in HomeView.tsx accordingly to follow the codebase convention.apps/investor-tokenization/src/features/transparency/ProjectList.tsx (1)
3-7: Use the repo path alias forProjectCard.This new local import bypasses the frontend
@/*convention.As per coding guidelines, "Use path alias `@/*` mapping to `./src/*` for imports".♻️ Suggested change
-import { ProjectCard } from "./ProjectCard"; +import { ProjectCard } from "@/features/transparency/ProjectCard";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/investor-tokenization/src/features/transparency/ProjectList.tsx` around lines 3 - 7, Replace the local relative import of ProjectCard with the repository path-alias import (use the `@/`* mapping to ./src/*) in ProjectList.tsx; locate the import statement referencing ProjectCard and change it to use the alias (e.g., import { ProjectCard } from "@/features/transparency/ProjectCard") so the project follows the frontend path-alias convention.apps/investor-tokenization/src/features/transparency/ProjectCard.tsx (1)
3-5: Pull shared primitives from@repo/ui.This new feature file is importing
Card,Badge, andButtonfrom@tokenization/ui, which diverges from the shared UI package the repo standardizes on.As per coding guidelines, "Import shared UI components from
@repo/uipackage (button, card, etc.)".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/investor-tokenization/src/features/transparency/ProjectCard.tsx` around lines 3 - 5, The file imports shared UI primitives from the wrong package; replace imports of Card, CardContent, Badge, and Button from "@tokenization/ui" with the standardized shared package "@repo/ui". Update the import statements that reference the symbols Card, CardContent, Badge, and Button so the components come from "@repo/ui" instead of "@tokenization/ui" and ensure any usage of these symbols in ProjectCard.tsx remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/investor-tokenization/src/components/shared/Sidebar.tsx`:
- Around line 53-55: The active-route check in the isActive assignment
incorrectly uses pathname?.startsWith(link.href) and will mark sibling routes
(e.g., /campaigns-old) as active; change the condition to match either exact
equality or a path boundary by using pathname === link.href ||
pathname?.startsWith(link.href + '/') (or equivalent check that ensures a
trailing '/' boundary) so that only the exact route or a subpath under that
route sets isActive; update the isActive assignment (where pathname and
link.href are referenced) accordingly.
In `@apps/investor-tokenization/src/features/home/HomeView.tsx`:
- Around line 39-44: The notifications button in HomeView (the <button> that
renders the Bell icon) is missing an accessible name; add an
aria-label="Notifications" attribute to that button element so screen readers
can identify it (mirror the approach used in RoiHeader for accessible icon-only
buttons).
In `@apps/investor-tokenization/src/features/tokens/components/InvestDialog.tsx`:
- Around line 334-341: The "Available balance" row in InvestDialog is showing
totalAmount (campaign milestones sum) instead of the investor's spendable wallet
or remaining investable amount; update the UI to either (A) display the actual
investor balance by replacing totalAmount with the correct value (e.g.
walletBalance, spendableBalance, or remainingAmount retrieved from the existing
balance hook/prop or by calling the balance fetching function used elsewhere),
or (B) if you intend to keep showing campaign target, rename the label from
"Available balance" to something like "Campaign target" to match totalAmount;
locate the span rendering totalAmount in the InvestDialog component and change
the bound variable or the label accordingly.
In `@apps/investor-tokenization/src/features/transparency/ProjectCard.tsx`:
- Around line 39-50: ProjectCard currently falls back to defaults and still
renders an actionable InvestDialog when tokenSale exists even if escrow is
missing; update the rendering logic so InvestDialog (and any invest CTA/trigger)
is only rendered and enabled when escrow is non-null and contains the required
campaign data (e.g., escrow.title/escrow.description/escrow.trustline) and when
isLoading is false; use the ProjectCard props (escrow, tokenSale, escrowId,
isLoading) to gate the InvestDialog rendering and disable the CTA otherwise
(also fix the same guard where InvestDialog is rendered around lines 106-125).
In `@apps/investor-tokenization/src/features/transparency/ProjectList.tsx`:
- Around line 57-70: filteredData's status filtering treats missing escrows as
"fundraising" and hides unresolved items for "active"; update the useMemo filter
to explicitly handle undefined escrows: use escrowsById lookup (escrow) and if
escrow is null/undefined return true so unresolved items are not excluded by any
status filter, change the "active" case to require escrow?.isActive === true
(but allow unresolved to pass through) and change the "fundraising" case to
require escrow?.isActive === false (do NOT use !escrow?.isActive), referencing
the filteredData useMemo, escrowsById, search and filter variables to locate and
fix the logic.
---
Nitpick comments:
In `@apps/investor-tokenization/src/app/campaigns/page.tsx`:
- Around line 16-25: The CampaignsPage currently owns local UI state and
composition (useState, RoiHeader, CampaignToolbar, ProjectList); extract this
composition into a new feature component (e.g., CampaignsView) under
src/features so the view owns the state (search, filter) and renders <RoiHeader
/>, <CampaignToolbar /> and <ProjectList />; then simplify page.tsx to import
and render the new CampaignsView as the default export, and update any
imports/usages accordingly so state and rendering live in the feature folder and
page.tsx remains a thin entrypoint.
In `@apps/investor-tokenization/src/app/roi/page.tsx`:
- Around line 17-35: The route file's state and view logic (useState hooks
search/setSearch and filter/setFilter plus the useMemo filteredCampaigns that
references mockCampaigns) should be extracted into a feature component under
src/features (e.g. create src/features/roi/RoiView or RoiFeature) that owns the
UI and filtering logic; update page.tsx (RoiPage) to be thin and only import and
render the new component (pass props only if needed), move the filtering code
and mockCampaigns usage into the new component (keeping function/component names
like RoiView or RoiFeature and the filteredCampaigns logic intact), export the
feature component as default or named export and remove the state/useMemo from
RoiPage so the App Router route stays declarative.
In `@apps/investor-tokenization/src/components/shared/Sidebar.tsx`:
- Around line 15-26: The Sidebar component currently hard-codes a local links
array (const links: SidebarLink[]) duplicating ROI_NAV_ITEMS from
roi-dashboard-shell.tsx; instead, move the nav model into a shared export under
src/features (e.g., export const ROI_NAV_ITEMS or a generic NAV_ITEMS in
src/features/roi/nav.ts), import that shared constant in both
apps/investor-tokenization/src/components/shared/Sidebar.tsx and
apps/investor-tokenization/src/features/roi/roi-dashboard-shell.tsx, and replace
the local links usage with the imported shared nav to keep labels/routes
centralized and avoid desynchronization.
In `@apps/investor-tokenization/src/features/home/HomeView.tsx`:
- Line 13: Replace the relative import in HomeView.tsx that pulls ProjectList
from "../transparency/ProjectList" with the project path alias import
"@/features/transparency/ProjectList" so the file imports ProjectList using the
`@/`* mapping; update the import statement in HomeView.tsx accordingly to follow
the codebase convention.
In `@apps/investor-tokenization/src/features/transparency/ProjectCard.tsx`:
- Around line 3-5: The file imports shared UI primitives from the wrong package;
replace imports of Card, CardContent, Badge, and Button from "@tokenization/ui"
with the standardized shared package "@repo/ui". Update the import statements
that reference the symbols Card, CardContent, Badge, and Button so the
components come from "@repo/ui" instead of "@tokenization/ui" and ensure any
usage of these symbols in ProjectCard.tsx remains unchanged.
In `@apps/investor-tokenization/src/features/transparency/ProjectList.tsx`:
- Around line 3-7: Replace the local relative import of ProjectCard with the
repository path-alias import (use the `@/`* mapping to ./src/*) in
ProjectList.tsx; locate the import statement referencing ProjectCard and change
it to use the alias (e.g., import { ProjectCard } from
"@/features/transparency/ProjectCard") so the project follows the frontend
path-alias convention.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f8e715cf-f90b-4489-aaa0-4a84bb8f4cba
⛔ Files ignored due to path filters (18)
apps/core/dist/app.module.jsis excluded by!**/dist/**apps/core/dist/app.module.js.mapis excluded by!**/dist/**,!**/*.mapapps/core/dist/loans/dto/create-loan.dto.d.tsis excluded by!**/dist/**apps/core/dist/loans/dto/create-loan.dto.jsis excluded by!**/dist/**apps/core/dist/loans/dto/create-loan.dto.js.mapis excluded by!**/dist/**,!**/*.mapapps/core/dist/loans/dto/update-loan.dto.d.tsis excluded by!**/dist/**apps/core/dist/loans/dto/update-loan.dto.jsis excluded by!**/dist/**apps/core/dist/loans/dto/update-loan.dto.js.mapis excluded by!**/dist/**,!**/*.mapapps/core/dist/loans/loans.controller.d.tsis excluded by!**/dist/**apps/core/dist/loans/loans.controller.jsis excluded by!**/dist/**apps/core/dist/loans/loans.controller.js.mapis excluded by!**/dist/**,!**/*.mapapps/core/dist/loans/loans.module.d.tsis excluded by!**/dist/**apps/core/dist/loans/loans.module.jsis excluded by!**/dist/**apps/core/dist/loans/loans.module.js.mapis excluded by!**/dist/**,!**/*.mapapps/core/dist/loans/loans.service.d.tsis excluded by!**/dist/**apps/core/dist/loans/loans.service.jsis excluded by!**/dist/**apps/core/dist/loans/loans.service.js.mapis excluded by!**/dist/**,!**/*.mapapps/investor-tokenization/public/interactuar_logo.pngis excluded by!**/*.png
📒 Files selected for processing (11)
apps/investor-tokenization/src/app/campaigns/layout.tsxapps/investor-tokenization/src/app/campaigns/page.tsxapps/investor-tokenization/src/app/layout.tsxapps/investor-tokenization/src/app/page.tsxapps/investor-tokenization/src/app/roi/page.tsxapps/investor-tokenization/src/components/shared/Sidebar.tsxapps/investor-tokenization/src/features/home/HomeView.tsxapps/investor-tokenization/src/features/roi/roi-dashboard-shell.tsxapps/investor-tokenization/src/features/tokens/components/InvestDialog.tsxapps/investor-tokenization/src/features/transparency/ProjectCard.tsxapps/investor-tokenization/src/features/transparency/ProjectList.tsx
| const isActive = | ||
| pathname === link.href || pathname?.startsWith(link.href); | ||
|
|
There was a problem hiding this comment.
Tighten the active-route match so sibling paths don't light up the wrong tab.
pathname?.startsWith(link.href) will also mark /campaigns-old or /roi-metrics as active. Match on the exact path or a / boundary instead.
Suggested change
- const isActive =
- pathname === link.href || pathname?.startsWith(link.href);
+ const isActive =
+ pathname === link.href || pathname.startsWith(`${link.href}/`);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const isActive = | |
| pathname === link.href || pathname?.startsWith(link.href); | |
| const isActive = | |
| pathname === link.href || pathname?.startsWith(`${link.href}/`); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/investor-tokenization/src/components/shared/Sidebar.tsx` around lines 53
- 55, The active-route check in the isActive assignment incorrectly uses
pathname?.startsWith(link.href) and will mark sibling routes (e.g.,
/campaigns-old) as active; change the condition to match either exact equality
or a path boundary by using pathname === link.href ||
pathname?.startsWith(link.href + '/') (or equivalent check that ensures a
trailing '/' boundary) so that only the exact route or a subpath under that
route sets isActive; update the isActive assignment (where pathname and
link.href are referenced) accordingly.
| <button | ||
| type="button" | ||
| className="relative rounded-md p-2 hover:bg-accent" | ||
| > | ||
| <Bell className="h-5 w-5 text-muted-foreground" /> | ||
| </button> |
There was a problem hiding this comment.
Add an accessible name to the icon-only notifications button.
This button renders only the bell icon, so screen readers won't get a usable label. Add aria-label="Notifications" like the RoiHeader version does.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/investor-tokenization/src/features/home/HomeView.tsx` around lines 39 -
44, The notifications button in HomeView (the <button> that renders the Bell
icon) is missing an accessible name; add an aria-label="Notifications" attribute
to that button element so screen readers can identify it (mirror the approach
used in RoiHeader for accessible icon-only buttons).
| <p className="text-xs text-muted-foreground"> | ||
| Available balance:{" "} | ||
| <span className="font-medium"> | ||
| {totalAmount > 0 | ||
| ? `${totalAmount.toLocaleString("en-US", { minimumFractionDigits: 2 })} ${currency}` | ||
| : `0.00 ${currency}`} | ||
| </span> | ||
| </p> |
There was a problem hiding this comment.
Available balance is showing the wrong value.
totalAmount is the sum of the campaign milestones, so this row is currently displaying the campaign target — not the connected wallet balance and not the remaining investable amount. Either fetch the investor's spendable balance here or rename the label to match the number being shown.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/investor-tokenization/src/features/tokens/components/InvestDialog.tsx`
around lines 334 - 341, The "Available balance" row in InvestDialog is showing
totalAmount (campaign milestones sum) instead of the investor's spendable wallet
or remaining investable amount; update the UI to either (A) display the actual
investor balance by replacing totalAmount with the correct value (e.g.
walletBalance, spendableBalance, or remainingAmount retrieved from the existing
balance hook/prop or by calling the balance fetching function used elsewhere),
or (B) if you intend to keep showing campaign target, rename the label from
"Available balance" to something like "Campaign target" to match totalAmount;
locate the span rendering totalAmount in the InvestDialog component and change
the bound variable or the label accordingly.
| export const ProjectCard = ({ | ||
| escrow, | ||
| escrowId, | ||
| tokenSale, | ||
| imageSrc, | ||
| isLoading = false, | ||
| }: ProjectCardProps) => { | ||
| const title = escrow?.title ?? "Loading..."; | ||
| const description = escrow?.description ?? ""; | ||
| const loansCompleted = getLoansCompleted(escrow); | ||
| const minInvest = getMinInvest(escrow); | ||
| const currency = escrow?.trustline?.symbol ?? "USDC"; |
There was a problem hiding this comment.
Disable the invest flow until escrow details are present.
After loading, this component falls back to placeholder copy/default values when escrow is missing, but it still renders a live InvestDialog whenever tokenSale exists. A failed or partial lookup should not leave the investment CTA actionable against incomplete campaign data.
🛡️ Minimal guard
export const ProjectCard = ({
escrow,
escrowId,
tokenSale,
imageSrc,
isLoading = false,
}: ProjectCardProps) => {
+ const hasEscrow = !!escrow;
const title = escrow?.title ?? "Loading...";
const description = escrow?.description ?? "";
const loansCompleted = getLoansCompleted(escrow);
const minInvest = getMinInvest(escrow);
const currency = escrow?.trustline?.symbol ?? "USDC";
@@
- {tokenSale ? (
+ {tokenSale && hasEscrow ? (
<SelectedEscrowProvider
value={{
escrow,
escrowId,
tokenSaleContractId: tokenSale,
imageSrc,
}}
>
<InvestDialog tokenSaleContractId={tokenSale} />
</SelectedEscrowProvider>
) : (
<Button
disabled
className="bg-orange-500 text-white hover:bg-orange-600"
>Also applies to: 106-125
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/investor-tokenization/src/features/transparency/ProjectCard.tsx` around
lines 39 - 50, ProjectCard currently falls back to defaults and still renders an
actionable InvestDialog when tokenSale exists even if escrow is missing; update
the rendering logic so InvestDialog (and any invest CTA/trigger) is only
rendered and enabled when escrow is non-null and contains the required campaign
data (e.g., escrow.title/escrow.description/escrow.trustline) and when isLoading
is false; use the ProjectCard props (escrow, tokenSale, escrowId, isLoading) to
gate the InvestDialog rendering and disable the CTA otherwise (also fix the same
guard where InvestDialog is rendered around lines 106-125).
| const filteredData = useMemo(() => { | ||
| return data.filter((item) => { | ||
| const escrow = escrowsById[item.escrowId]; | ||
| if (search) { | ||
| const q = search.toLowerCase(); | ||
| const title = (escrow?.title ?? "").toLowerCase(); | ||
| const desc = (escrow?.description ?? "").toLowerCase(); | ||
| if (!title.includes(q) && !desc.includes(q)) return false; | ||
| } | ||
| if (filter === "active") return escrow?.isActive === true; | ||
| if (filter === "fundraising") return !escrow?.isActive; | ||
| return true; | ||
| }); | ||
| }, [search, filter, escrowsById]); |
There was a problem hiding this comment.
Don't turn unresolved escrows into a real filter state.
!escrow?.isActive makes missing data look "fundraising", while the "active" branch hides every card until the query resolves. That drops the loading skeletons for one filter and misclassifies fetch misses for the other.
💡 One safe way to keep loading/error states out of the status filters
const filteredData = useMemo(() => {
return data.filter((item) => {
const escrow = escrowsById[item.escrowId];
+ if (!escrow) return isLoading;
+
if (search) {
const q = search.toLowerCase();
const title = (escrow?.title ?? "").toLowerCase();
const desc = (escrow?.description ?? "").toLowerCase();
if (!title.includes(q) && !desc.includes(q)) return false;
}
- if (filter === "active") return escrow?.isActive === true;
- if (filter === "fundraising") return !escrow?.isActive;
+ if (filter === "active") return escrow.isActive === true;
+ if (filter === "fundraising") return escrow.isActive === false;
return true;
});
- }, [search, filter, escrowsById]);
+ }, [search, filter, escrowsById, isLoading]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/investor-tokenization/src/features/transparency/ProjectList.tsx` around
lines 57 - 70, filteredData's status filtering treats missing escrows as
"fundraising" and hides unresolved items for "active"; update the useMemo filter
to explicitly handle undefined escrows: use escrowsById lookup (escrow) and if
escrow is null/undefined return true so unresolved items are not excluded by any
status filter, change the "active" case to require escrow?.isActive === true
(but allow unresolved to pass through) and change the "fundraising" case to
require escrow?.isActive === false (do NOT use !escrow?.isActive), referencing
the filteredData useMemo, escrowsById, search and filter variables to locate and
fix the logic.
Made-with: Cursor
Summary
/campaigns): Moved the "Invest in Campaigns" view from the home page to/campaignswith its own layout and page, reusingRoiDashboardShell,RoiHeader, andCampaignToolbarcomponents./redirects to/campaigns. Layout simplified to only providers. Fixed z-index conflict that was hiding the dialog behind the shell overlay.ProjectCardupdated with teal "FUNDRAISING" badge, vertical layout, and orange "Invest" button.ProjectListnow accepts search/filter props with client-side filtering.CampaignToolbardynamically imported withssr: falseto prevent Radix Select ID mismatch between server and client.Test plan
localhost:3001→ should redirect to/campaigns/campaignsshows sidebar, header with search/bell, filter dropdown, and project cards/roipage works with same shell layoutSummary by CodeRabbit
New Features
Improvements